package org.xteam.camllight.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.RandomAccessFile;

import org.xteam.camllight.Atom;

public class Channel {

	private static final int IO_BUFFER_SIZE = 4096;

	private IFile fd;
	private int offset = 0;
	private int curr = 0;
	private int max = 0;
	private byte[] buff = new byte[IO_BUFFER_SIZE];
	private int end = IO_BUFFER_SIZE;

	public Channel(RandomAccessFile file) {
		fd = new RandomAccessFileImpl(file);
	}

	public Channel(InputStream stream) {
		fd = new InputStreamFileImpl(stream);
	}

	public Channel(PrintStream stream) {
		fd = new PrintStreamFileImpl(stream);
	}

	public int getWord() {
		int res = 0;
		for (int i = 0; i < 4; i++) {
			res = (res << 8) + getch();
		}
		return res;
	}

	public int getch() {
		if (curr >= max) {
			return refill();
		}
		return buff[curr++] & 0xff;
	}

	private int refill() {
		try {
			int n = fd.read(buff, 0, buff.length); // really_read
			if (n == 0)
				throw new RuntimeException("END_OF_FILE_EXN"); // mlraise(Atom(END_OF_FILE_EXN));
			offset += n;
			max = n;
			curr = 1;
		} catch (Exception e) {
			throw new RuntimeException("END_OF_FILE_EXN");
		}
		return buff[0] & 0xff;
	}

	public boolean really_getblock(byte[] block, int size) {
		int r;
		int p = 0;
		while (size > 0) {
			r = getblock(block, p, size);
			if (r == 0) {
				return false;
			}
			p += r;
			size -= r;
		}
		return true;
	}

	public int getblock(byte[] block, int p, int n) {
		int l;

		int m = max - curr;
		if (n <= m) {
			System.arraycopy(buff, curr, block, p, n);
			curr += n;
			return n;
		}
		if (m > 0) {
			System.arraycopy(buff, curr, block, p, m);
			curr += m;
			return m;
		}
		if (n < IO_BUFFER_SIZE) {
			try {
				l = fd.read(buff, 0, IO_BUFFER_SIZE);
			} catch (IOException e) {
				throw new RuntimeException("IO");
			}
			offset += l;
			max = l;
			if (n > l) {
				n = l;
			}
			System.arraycopy(buff, 0, block, p, n);
			curr = n;
			return n;
		}
		curr = 0;
		max = 0;
		try {
			l = fd.read(block, p, n);
		} catch (IOException e) {
			throw new RuntimeException("IO");
		}
		offset += l;
		return l;
	}

	public Object close() {
		fd.close();
		return Atom.get(0);
	}

	public void putblock(byte[] data, int start, int n) {
		try {
			int m = end - curr;
			if (curr == 0 && n >= m) {
				fd.write(data, start, n);
				offset += n;
			} else if (n <= m) {
				System.arraycopy(data, start, buff, curr, n);
				curr += n;
				if (curr > max) {
					max = curr;
				}
			} else {
				System.arraycopy(data, start, buff, curr, m);
				start += m;
				n -= m;
				m = end;
				fd.write(buff, 0, m);
				offset += m;
				if (n <= m) {
					System.arraycopy(data, start, buff, 0, n);
					curr = max = n;
				} else {
					fd.write(data, start, n);
					offset += n;
					curr = max = 0;
				}
			}
		} catch (IOException e) {
			throw new RuntimeException("IO");
		}
	}

	public void putch(int value) {
		if (curr >= end) {
			flush();
		}
		buff[curr++] = (byte) value;
		if (curr > max) {
			max = curr;
		}
	}

	public Object flush() {
		try {
			int n = max;
			if (n > 0) {
				fd.write(buff, 0, n);
				offset += n;
				curr = 0;
				max = 0;
			}
			return Atom.get(0);
		} catch (IOException e) {
			throw new RuntimeException("IO");
		}
	}

}
